﻿//Copyright (C) <2013>  <jonathan cleeve norton> All Rights Reserved 
//Contact jon.norton@fin-plus.co.uk website <http://www.fin-plus.co.uk/>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Reflection;
using System.ComponentModel;
using u = FinPlusUtility.Utility;
using log = Logger.Logger;

namespace FinPlusCommon
{
    public static class FinPlusAssembler
    {
        private enum Params { Parent, Child, Dock, Initialise, Model, Id, FunctionName, FinPlusChildService, TimeStamp, TestStates, States, CommandRouter, Dispose }
        private static IList<string> _exclude = new List<string>() { Params.Dock.ToString() };
        
        //construct
        public static IFinPlusFunction Construct(IFinPlusFunctions funcs, Assembly[] targetAssembly, string returnFuncName, bool initilizeOnly = false, u.Env env = u.Env.unknown)
        {
            try
            {
                foreach (var func in funcs.Values)
                {
                    if (!initilizeOnly && func.Object == null && func[Params.Parent.ToString()].ToString() != string.Empty)
                    {
                        //Abstract Factory style configd depending
                        func.Object = new object();//stops repeat calls an not all methods return result
                        var parent = Construct(funcs, targetAssembly, func[Params.Parent.ToString()].ToString(), true, env);//construct parent
                        var child = Construct(funcs, targetAssembly, func[Params.Child.ToString()].ToString(), true, env);//construct only
                        InvokeMethod(func, parent, child);//call method
                    }
                    else if (func[Params.Id.ToString()].Equals(returnFuncName))
                    {
                        if (func.Object != null) return func;

                        //IOC style config depending
                        var parent = Construct(func, targetAssembly, env, _exclude);//construct parent
                        if (parent[Params.Dock.ToString()].ToString() != string.Empty)
                             InjectChildren(funcs, parent, targetAssembly, env);

                        Initialise(parent);

                        return parent;
                    }
                 }
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, "NA", "NA", e.Message, e);
            }

            return null;
        }

        public static IFinPlusFunction Construct(IFinPlusFunction func, Assembly[] assemblys, u.Env env, IList<string> exclude = null)
        {
            object[] parms = new[] { "null" };
            var funcName = "Unknown";

            try
            {
                if (func.Object == null) //only build if not built yet
                {
                    foreach (var assembly in assemblys)
                    {
                        funcName = func[Params.FunctionName.ToString()].ToString();
                        func.Type = Type(assembly, funcName);
                        
                        if (func.Type == null) continue;

                        parms = func.ToParamArray(null, exclude);
                        func.Invoke = string.Format("var {0} = new {1}({2})", func[Params.Id.ToString()].ToString(), funcName, func.ToParamString());
                        func.Object = Activator.CreateInstance(func.Type, parms);
                        InjectItemState(func, new Item(0, -1, Params.Id.ToString(), func[Params.Id.ToString()].ToString(), "", DateTime.Now, ""));//add func id to object
                        
                        return func;
                    }
                }
            }
            catch (Exception e)
            {
                //logs get caught in failed assembly plus failed object get diplayed
                func.Object = new Exception(string.Format("Contact support failed to construct {0} with params {1}", funcName, u.StringFromArray1d(parms)), e);
            }

            return func;
        }

        //public
        public static void InjectChildren(IFinPlusFunctions funcs, IFinPlusFunction parent, Assembly[] targetAssembly, u.Env env = u.Env.unknown)
        {
            foreach (var dock in parent[Params.Dock.ToString()].ToString().Split(';'))
            {
                var dockParams = dock.Split(':');

                var child = Construct(funcs, targetAssembly, dockParams[0], true, env);//construct only

                var dockFunc = new Function(Params.Dock.ToString());
                dockFunc.Add(Params.Parent.ToString(), parent.Name);
                dockFunc.Add(Params.Child.ToString(), child.Name);
                dockFunc.Add(Params.Dock.ToString(), dockParams.Length > 1 ? dockParams[1] : string.Empty);

                InvokeMethod(dockFunc, parent, child);//dock
            }
        }

        public static void Initialise(IFinPlusFunction parent)
        {
           var func = new Function(Params.Initialise.ToString());
           InvokeMethod(func, parent);//Initialise
        }

        public static object InvokeMethod(IFinPlusFunction func, IFinPlusFunction parentFunc, IFinPlusFunction childFunc = null, IList<string> exclude = null, bool dateTimeToInt = false)
        {
            object[] parms = new[] { "null" };
            var funcName = "Unknown";

            var parentId = parentFunc[Params.Id.ToString()].ToString();
            var childId = childFunc == null ? "null" : childFunc[Params.Id.ToString()].ToString();

            parentFunc.Children.Add(childId);
            if(childFunc != null) 
                childFunc.Parents.Add(parentId);

            try
            {
                funcName = func.Name;
                var methodInfo = parentFunc.Type.GetMethod(funcName);
                parms = func.ToParamArray(childFunc == null ? null : childFunc.Object, exclude, dateTimeToInt);
                func.Invoke = string.Format("{0}.{1}({2})", parentId, funcName, func.ToParamString(childId));

                object res = methodInfo.Invoke(parentFunc.Object, parms);
                if (res != null)
                    func.Object = res;

            }
            catch (Exception e)
            {
                //logs get caught in failed assembly plus failed object get displayed
                func.Object = new Exception(string.Format("Contact support failed to call method {0} with params {1}", funcName, u.StringFromArray1d(parms)), e);
            }

            return func.Object;
        }

        public static IFinPlusFunctions ExtractState(IFinPlusFunction parentFunc, string userName = "", bool testStates = false, IFinPlusFunctions statFuncs = null)
        {
            statFuncs = statFuncs == null ? new Functions() : statFuncs;
            userName = string.IsNullOrEmpty(userName) ? Environment.UserName : userName;

            try
            {
                //get parent states
                var parentFuncState = FuncState(parentFunc, userName, testStates);
                if (parentFuncState != null) statFuncs.Add(parentFuncState, true, false);

                //get children 
                var model = Property(parentFunc, Params.Model.ToString());
                if (model == null) return statFuncs;

                //get children state
                foreach (var childFunc in ((Functions)model).Values)
                    ExtractState(childFunc, userName, testStates, statFuncs);
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, string.Format("@{0}@{1}@{2}", "NA", "NA", e.Message), e);
            }
            return statFuncs;
        }

        public static void InjectState(IFinPlusFunction parentFunc, IFinPlusFunctions stateFuncs)
        {
            try
            {
                //set parent states
                var Id = Property(parentFunc, Params.Id.ToString());
                if (Id == null) return;

                foreach (var stateFunc in stateFuncs.Values)
                    if (Id.ToString() == stateFunc[Params.Id.ToString()].ToString())
                            InjectFuncState(parentFunc, stateFunc);

                //get children funcs
                var model = Property(parentFunc, Params.Model.ToString());
                if (model == null) return;

                //set childFunc state
                foreach (var childFunc in ((Functions)model).Values)
                    InjectState(childFunc, stateFuncs);
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, "NA", "NA",  e.Message, e);
            }
        }

        public static void InjectCommands(IFinPlusFunction parentFunc, IFinPlusFunctions eventFuncs)
        {
            try
            {
                foreach (var eventFunc in eventFuncs.Values)
                    InjectCommand(parentFunc, eventFunc);
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, "NA", "NA", e.Message, e);
            }
        }

        public static IFinPlusFunctions Dispose(IFinPlusFunctions funcs)
        {
            var dispose = new Functions();

            if (funcs.Values == null) return dispose;

            foreach (var func in funcs.Values)
            {
                if (func.Object != null && func.Type != null) 
                {
                    try
                    {
                        var methodInfo = func.Type.GetMethod(Params.Dispose.ToString());
                        object res = methodInfo.Invoke(func.Object, null);
                    }
                    catch (Exception e)
                    {
                        func.Object = e;
                        dispose.TryAdd(func.Id, func);
                    }
                }
            }

            return dispose;
        }

        public static IFinPlusFunctions Exceptions(IFinPlusFunctions funcs)
        {
            var exceptions = new Functions();

            foreach (var func in funcs.Values)
                if (func.Object.GetType() == typeof(Exception))
                    exceptions.TryAdd(func.Id, func);

            return exceptions;
        }

        public static IFinPlusFunctions NullObjects(IFinPlusFunctions funcs)
        {
            var nullObject = new Functions();

            foreach (var func in funcs.Values)
                if (func.Object == null)
                    nullObject.TryAdd(func.Id, func);

            return nullObject;
        }

        public static IFinPlusFunctions CircularRef(IFinPlusFunctions funcs)
        {
            //TODO
            var circularReference = new Functions();
            return circularReference;
        }

        //private
        private static IFinPlusFunction FuncState(IFinPlusFunction parentFunc, string userName, bool testStates)
        {
            try
            {
                //get id
                var Id = Property(parentFunc, Params.Id.ToString());
                if(Id == null) return null;
                 
                //get states          
                var states = Property(parentFunc, testStates ? Params.TestStates.ToString() : Params.States.ToString());
                if (states == null) return null;

                //get state func
                var stateFunc = new Function(parentFunc.Object.GetType().Name, Id.ToString(), userName);
                var pos = 4;
                foreach (var state in (string[])states)
                {
                    var item = ItemState(parentFunc, state, pos, userName);
                    if (item == null) continue;
                    stateFunc.TryAdd(item);
                    pos++;
                }

                //if has state return func
                return pos > 4 ? stateFunc : null;
            }
            catch(Exception e)
            {
                log.Log(Logger.Level.Error, "NA", "NA",  e.Message, e);
                return null;
            }
        }

        private static IFinPlusItem ItemState(IFinPlusFunction parentFunc, string state, int pos, string userName)
        {
            try
            {
                //get state value
                var value = Property(parentFunc, state);
                if (value == null) return null;

                //get state type
                var type = value.GetType().UnderlyingSystemType.Name;

                //make state item
                var item = new Item(pos, parentFunc.Id, state, value.ToString(), type, DateTime.Now, userName);

                return item;
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, "NA", "NA", e.Message, e);
                return null;
            }
        }

        private static void InjectFuncState(IFinPlusFunction targetFunc, IFinPlusFunction stateFunc)
        {
            foreach (var stateItem in stateFunc)
                if (stateItem.Name != Params.Id.ToString() && stateItem.Name != Params.FunctionName.ToString() && stateItem.Name != "TimeStamp")
                    InjectItemState(targetFunc, stateItem);
        }

        private static void InjectItemState(IFinPlusFunction targetFunc, IFinPlusItem stateItem)
        {
            try
            {
                if (HasProperty(targetFunc, stateItem.Name))
                {
                    var pInfo = targetFunc.Object.GetType().GetProperty(stateItem.Name);
                    pInfo.SetValue(targetFunc.Object, stateItem.Object, null);
                }
            }
            catch
            {

            }
        }

        private static void InjectCommand(IFinPlusFunction parentFunc, IFinPlusFunction eventFunc)
        {
            var model = Property(parentFunc, Params.Model.ToString());
            if (model == null) return;

            foreach (var childFunc in ((Functions)model).Values)
            {
                var childId = Property(childFunc, Params.Id.ToString());
                if (childId != null && childId.ToString() == eventFunc[Params.Id.ToString()].ToString())
                    InvokeCommandRouter(childFunc, eventFunc);
                else
                    InjectCommand(childFunc, eventFunc);
            }
        }

        private static void InvokeCommandRouter(IFinPlusFunction targetFunc, IFinPlusFunction eventFunc)
        {
            try
            {
                var methodInfo = targetFunc.Object.GetType().GetMethod(Params.CommandRouter.ToString());
                var parms = new object[] { eventFunc };
                object res = methodInfo.Invoke(targetFunc.Object, parms);
            }
            catch (Exception e)
            {
                eventFunc.Object = e;
            }
        }
 
        private static Type Type(Assembly assembly, string name)
        {
            foreach (var type in assembly.GetExportedTypes())
                if (type.Name == name)
                    return type;

          
            return null;
        }

        private static object Property(IFinPlusFunction parentFunc, string propName)
        {
            try
            {
                if (!HasProperty(parentFunc, propName)) return null;

                var obj = parentFunc.Object.GetType().GetProperty(propName).GetValue(parentFunc.Object, null);
                return obj;
            }
            catch
            {
                return null;
            }
        }

        private static bool HasProperty(IFinPlusFunction func, string name)
        {
            if (func == null || func.Object == null) return false;
            var type = func.Object.GetType();
            return type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public) != null;
        }

        private static bool HasMethod(IFinPlusFunction func, string name)
        {
            var type = func.Object.GetType();
            return type.GetMethod(name) != null;
        }
    }
}

























































































































































































//Copyright2013<jnrton>AllRightsReserved 
//Contact j.nrton@finpus